package com.gitee.kamismile.stone.rpc.core.provider.producer.assembly;

import com.fasterxml.jackson.databind.node.POJONode;
import com.gitee.kamismile.stone.commmon.util.ValueUtils;
import com.gitee.kamismile.stone.rpc.common.properties.StoneRpcConfigProperties;
import com.gitee.kamismile.stone.rpc.common.support.StoneRpcEnum;
import com.gitee.kamismile.stone.rpc.api.engine.StoneAppEngine;
import com.gitee.kamismile.stone.rpc.common.support.StoneURL;
import com.gitee.kamismile.stone.rpc.core.provider.producer.StoneRpcReference;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;

public class StoneRpcReferenceProcessor implements BeanPostProcessor, EmbeddedValueResolverAware {

  private StringValueResolver resolver;

  private StoneAppEngine stoneAppEngine;

  private StoneRpcConfigProperties stoneRpcConfigProperties;

  private ApplicationContext applicationContext;

  public StoneRpcReferenceProcessor(StoneRpcConfigProperties stoneRpcConfigProperties,
      StoneAppEngine stoneAppEngine, ApplicationContext applicationContext) {
    this.stoneRpcConfigProperties = stoneRpcConfigProperties;
    this.applicationContext = applicationContext;
    this.stoneAppEngine = stoneAppEngine;
  }

  @Override
  public void setEmbeddedValueResolver(StringValueResolver resolver) {
    this.resolver = resolver;
  }

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    // 扫描所有field，处理扩展的field标注
    ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
      public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
        processConsumerField(bean, field);
      }
    });

    return bean;
  }


  protected void processConsumerField(Object bean, Field field) {
    StoneRpcReference reference = field.getAnnotation(StoneRpcReference.class);
    if (reference == null) {
      return;
    }

    Class<?> fieldType = field.getType();

    if (!fieldType.isInterface() && Modifier.isAbstract(fieldType.getModifiers())) {
      Assert.isTrue(true, "StoneReference must interface ");
      return;
    }

    handleReferenceField(bean, field, reference);
  }

  private void handleReferenceField(Object bean, Field field, StoneRpcReference reference) {
    Class<?> interfaceType = field.getType();
    Map<String, StoneRpcConfigProperties.StoneServerProvider> servers = this.stoneRpcConfigProperties.getServers();
    Assert.notEmpty(servers, "servers is empty");

    StoneRpcConfigProperties.StoneServerProvider currentProvider = null;
    for (StoneRpcConfigProperties.StoneServerProvider provider : servers.values()) {
      Assert.notEmpty(provider.getBasePackages(), "basePackages is empty");
      boolean present = provider.getBasePackages().stream()
          .filter(v -> (interfaceType.getPackage().getName()+".").contains(v+".")).findFirst().isPresent();
      if (present) {
        currentProvider = provider;
        break;
      }
    }

    Assert.notNull(currentProvider, "servers is empty");

    StonePojoReferenceMeta stonePojoReferenceMeta = new StonePojoReferenceMeta();
    stonePojoReferenceMeta.setMapperInterface(interfaceType);
    stonePojoReferenceMeta.setTimeout(currentProvider.getTimeout());
    stonePojoReferenceMeta.setServerName(currentProvider.getServerName());
    stonePojoReferenceMeta.setServerVersion(currentProvider.getServerVersion());
    stonePojoReferenceMeta.setApplicationContext(applicationContext);
    stonePojoReferenceMeta.setStoneAppEngine(stoneAppEngine);
    stonePojoReferenceMeta.setRetry(currentProvider.getRetry());
    StoneURL stoneURL = new StoneURL();
    stoneURL.setUsername(currentProvider.getUsername());
    stoneURL.setPassword(currentProvider.getPassword());
    stoneURL.setServiceKey(currentProvider.getServiceKey());
    stoneURL.setServerName(currentProvider.getServerName());
    Map<String, String> serverAttributes = currentProvider.getServerAttributes();
    String host = serverAttributes.get("host");
    String port = serverAttributes.get("port");
    if (!StringUtils.isEmpty(host)) {
      Assert.isTrue(NumberUtils.isDigits(port), "servers port must be number");
      stoneURL.setHost(host);
      stoneURL.setPort(ValueUtils.isIntegerNull(port));
    }
    stonePojoReferenceMeta.setStoneURL(stoneURL);
    if (reference.timeout() > 0) {
      stonePojoReferenceMeta.setTimeout(reference.timeout());
    }

    if (reference.retry() > 0) {
      stonePojoReferenceMeta.setRetry(reference.retry());
    }

    if (stonePojoReferenceMeta.getTimeout() < 1) {
      stonePojoReferenceMeta.setTimeout(StoneRpcEnum.TIMEOUT.getCode());
    }

    if (stonePojoReferenceMeta.getRetry() < 0) {
      stonePojoReferenceMeta.setRetry(StoneRpcEnum.RETRY.getCode());
    }

    stoneURL.setTimeout(stonePojoReferenceMeta.getTimeout());
    stoneURL.setRetry(stonePojoReferenceMeta.getRetry());
    Map<String, String> parameters = stoneURL.getParameters();
    parameters.put("timeout", String.valueOf(stoneURL.getTimeout()));
    parameters.put("retry", String.valueOf(stoneURL.getRetry()));
    stonePojoReferenceMeta.afterPropertiesSet();

    ReflectionUtils.makeAccessible(field);
    ReflectionUtils.setField(field, bean, stonePojoReferenceMeta.getProxy());
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }
}
